This notebook is for working with DBSCAN using the tibbles created by data_processing.rmd

# Load in libraries 
library(beeswarm)
library(naniar)
library(zoo)
library(janitor)
library(dplyr)
library(plyr)
library(tidyverse)
library(ggplot2)
library(GGally) # for ggpairs
library(lubridate)

# For PCA visualization
# install.packages("factoextra")
library(factoextra)
# install.packages("dbscan")
library(dbscan)
library(RColorBrewer)
plot_vs_county <- function(df, col_val, percentile=FALSE,
                           fips_title="county_fips_code", banks=6, 
                           legend_title="", graphic_title=""){
  # Subset for speed 
  df <- df[c(fips_title, col_val)]
  
  # Get county data
  gcounty <- ggplot2::map_data("county")
  # USA map data
  gusa <- map_data("state")

  if (banks > 9){
    mycolors <- colorRampPalette(brewer.pal(9, "Reds"))(banks)
  }

  # Format with subregions
  fipstab <-
      transmute(maps::county.fips, fips, county = sub(":.*", "", polyname)) %>%
      unique() %>%
      separate(county, c("region", "subregion"), sep = ",")

  # Combine in desired order (NA for missing)
  gcounty <- left_join(gcounty, fipstab, c("region", "subregion"))


  dis <- df
  dis$rprop <- rank(df[col_val])
  dis$pcls <- cut(100 * percent_rank(df[col_val]), seq(0, 100, len = banks),
                        include.lowest = TRUE)

  # Missing data
  anti_join(gcounty, dis, by = c("fips" = fips_title)) %>%
    select(region, subregion) %>%
    unique()
  gcounty_pop <- left_join(gcounty, dis, by = c("fips" = fips_title))
  fill_vals <- gcounty_pop[col_val]

  # Plot
  if (legend_title == ""){
    legend_title <- col_val
  }

  if (percentile == FALSE){
    # names(gcounty_pop)[names(gcounty_pop) == col_val] <- "col_of_interest"
    plt <- ggplot(gcounty_pop) +
      geom_polygon(aes(long, lat, group = group, fill = get(col_val)),
                   color = "grey", size = 0.1, name="Percent Infected") +
      geom_polygon(aes(long, lat, group = group),
                   fill = NA, data = gusa, color = "lightgrey") +
      coord_map("bonne", parameters = 41.6) + ggthemes::theme_map() +
      theme(plot.title = element_text(family = "Helvetica", face = "bold", size = (15)),
          legend.background = element_rect(fill = NA),
          legend.position = "left")
      # scale_fill_gradient2()
       # scale_fill_gradient(low = "white", high = "red", na.value = "grey")
      # scale_fill_gradientn(colours = terrain.colors(10))
  }

if (percentile == TRUE){
  plt <- ggplot(gcounty_pop) +
    geom_polygon(aes(long, lat, group = group, fill = pcls),
                 color = "grey", size = 0.1) +
    geom_polygon(aes(long, lat, group = group),
                 fill = NA, data = gusa, color = "lightgrey") +
    coord_map("bonne", parameters = 41.6) + ggthemes::theme_map() +
    scale_fill_manual(values = mycolors, na.value = "grey") +
    # scale_fill_brewer(palette = "viridis", na.value = "grey") +
    theme(plot.title = element_text(family = "Helvetica", face = "bold", size = (15)),
          legend.background = element_rect(fill = NA),
          legend.position = "left")
    }
  plt <- plt + labs(fill=legend_title) + ggtitle(graphic_title)
  plt
}
census_normed <- read_csv("./../datasets/census_normed.csv")

── Column specification ──────────────────────────────────────────────────────────────────────────────────────────
cols(
  county_fips_code = col_double(),
  county_name = col_character(),
  state = col_character(),
  pct_infected = col_double(),
  pct_deaths = col_double(),
  mortality_rate = col_double(),
  pct_elderly = col_double(),
  pct_male_elderly = col_double(),
  pct_female_elderly = col_double(),
  pct_male_population = col_double(),
  pct_female_population = col_double(),
  pct_worked_at_home = col_double(),
  median_income = col_double(),
  median_age = col_double()
)
agg_state_info_normed <- read_csv("./../datasets/agg_state_info_normed.csv")

── Column specification ──────────────────────────────────────────────────────────────────────────────────────────
cols(
  state = col_character(),
  pct_infected = col_double(),
  pct_deaths = col_double(),
  mortality_rate = col_double(),
  pct_elderly = col_double(),
  pct_male_elderly = col_double(),
  pct_female_elderly = col_double(),
  pct_male_population = col_double(),
  pct_female_population = col_double(),
  pct_worked_at_home = col_double(),
  mean_median_income = col_double(),
  mean_median_age = col_double()
)
# Add in actual state name to avoid issues 
agg_state_info_normed$region = tolower(state.name[match(agg_state_info_normed$state, state.abb)])

census_normed <- census_normed %>% mutate(county_fips_code = as.integer(county_fips_code))


# Adding in county, state for graphs later
# Remove ' county'
# Go ahead and drop na values to avoid issues 
census_normed$county_name <- sub("\\s*County\\b.*", "", census_normed$county_name)
census_normed <- census_normed %>% mutate(county_state = paste(county_name, state, sep=", ")) %>% drop_na()

census_normed
agg_state_info_normed
census_normed %>% select_if(is.double)

Cluster With DBSCAN

First, we need to find a good value for eps. Note: minPts contains the point itself, while the k-nearest neighbor does not. We therefore have to use k = minPts - 1. I will create a utility function to plot to find the knee in the graph, but the dotted line will have to be adjusted after it is found.

# Note: k should be minpoints - 1!
find_optimal_eps <- function(data, k, line_pos=.32){
  kNNdistplot(data, k = k)
  abline(h = line_pos, col = "red", lty=2)
}

add_cluster_columns <- function(data, cluster_df){
  data %>% mutate(cluster = cluster_df$cluster)
}

add_noise_column <- function(dbscan_df, noise_col=0){
  clustered_data <- dbscan_df %>% mutate(cluster = if_else(cluster == noise_col, "Noise", as.character(cluster)))
}

# Add a plotting function
# Procedure:
minPts <- 10 #10 counties must be in a cluster at a minimum

# 1. Get the features we want to cluster with
cluster_features <- census_normed %>% select(mortality_rate, pct_elderly) %>%
  drop_na()

# 2. Find eps value
find_optimal_eps(cluster_features, k = minPts-1, line_pos = .7)


# 3. Make the clustering 
cluster_labeling <- cluster_features %>% dbscan(eps=.7, MinPts = minPts)
converting argument MinPts (fpc) to minPts (dbscan)!
print(cluster_labeling)
DBSCAN clustering for 3139 objects.
Parameters: eps = 0.7, minPts = 10
The clustering contains 1 cluster(s) and 27 noise points.

   0    1 
  27 3112 

Available fields: cluster, eps, minPts
# 4. Add clustering Labels
cluster_data <- add_cluster_columns(cluster_features, cluster_labeling)

# 5. (Depending) Add noise label
cluster_data <- add_noise_column(cluster_data)

# 6. Add back the county/state info
cluster_data <- left_join(cluster_data, census_normed)
Joining, by = c("mortality_rate", "pct_elderly")
# 5. Plot the clustering 
ggplot(cluster_data,
  aes(x=pct_elderly, y=mortality_rate, color=cluster))+
  geom_point()+
  labs(y = "Mortality Rate", x = "Percentage of Elderly Population", color="Cluster")+
  geom_text(aes(label= ifelse(mortality_rate > quantile(mortality_rate, 0.999),
     as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
  geom_text(aes(label= ifelse(pct_elderly > quantile(pct_elderly, 0.999),
     as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
  scale_x_continuous(expand = c(.1, .1))+
  scale_fill_discrete(name = "Cluster")


# 6. Geography Plot
plot_vs_county(cluster_data, "cluster", 
               percentile = F,
               banks = 11,
               legend_title = "Cluster",
               graphic_title = "Mortality Rate vs. Percentage Elderly Population Clustering")
Ignoring unknown parameters: name

Non useful ones that look the exact same:

census_normed
# Procedure:
minPts <- 10 #10 counties must be in a cluster at a minimum

interested_features = c("median_age", "mortality_rate")

# 1. Get the features we want to cluster with
cluster_features <- census_normed %>% select(interested_features) %>%
  drop_na()

# 2. Find eps value
find_optimal_eps(cluster_features, k = minPts-1, line_pos = .7)


# 3. Make the clustering 
cluster_labeling <- cluster_features %>% dbscan(eps=.7, MinPts = minPts)
converting argument MinPts (fpc) to minPts (dbscan)!
print(cluster_labeling)
DBSCAN clustering for 3139 objects.
Parameters: eps = 0.7, minPts = 10
The clustering contains 1 cluster(s) and 18 noise points.

   0    1 
  18 3121 

Available fields: cluster, eps, minPts
# 4. Add clustering Labels
cluster_data <- add_cluster_columns(cluster_features, cluster_labeling)

# 5. (Depending) Add noise label
cluster_data <- add_noise_column(cluster_data)
# print(cluster_data)

# 6. Add back the county/state info
# cluster_data <- left_join(cluster_data, census_normed, by=state_county)
cluster_data <- census_normed %>% mutate(cluster = cluster_data$cluster)
print(cluster_data)

# 5. Plot the clustering 
ggplot(cluster_data,
  aes(x=get(interested_features[1]), y=get(interested_features[2]), color=cluster))+
  geom_point()+
  labs(y = "Mortality Rate", x = "Median Age", color="Cluster")+
  geom_text(aes(label= ifelse(get(interested_features[1]) > quantile(get(interested_features[1]), 0.999),
     as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
  geom_text(aes(label= ifelse(get(interested_features[2]) > quantile(get(interested_features[2]), 0.999),
     as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
  scale_x_continuous(expand = c(.1, .1))+
  scale_fill_discrete(name = "Cluster")


# 6. Geography Plot
plot_vs_county(cluster_data, "cluster", 
               percentile = F,
               banks = 11,
               legend_title = "Cluster",
               graphic_title = "Median Age and Mortality Rate Clustering")
Ignoring unknown parameters: name

# Procedure:
minPts <- 10 #10 counties must be in a cluster at a minimum

# 1. Get the features we want to cluster with
cluster_features <- census_normed %>% select_if(is.double) %>%
  drop_na() %>% select(-one_of(c("pct_infected", "pct_deaths", "mortality_rate")))

# 2. Find eps value
find_optimal_eps(cluster_features, k = minPts-1, line_pos = .7)


# 3. Make the clustering 
cluster_labeling <- cluster_features %>% dbscan(eps=.7, MinPts = minPts)
converting argument MinPts (fpc) to minPts (dbscan)!
print(cluster_labeling)
DBSCAN clustering for 3139 objects.
Parameters: eps = 0.7, minPts = 10
The clustering contains 2 cluster(s) and 925 noise points.

   0    1    2 
 925 2191   23 

Available fields: cluster, eps, minPts
# 4. Add clustering Labels
cluster_data <- add_cluster_columns(cluster_features, cluster_labeling)

# 5. (Depending) Add noise label
cluster_data <- add_noise_column(cluster_data)
# print(cluster_data)

# 6. Add back the county/state info
# cluster_data <- left_join(cluster_data, census_normed, by=state_county)
cluster_data <- census_normed %>% mutate(cluster = cluster_data$cluster)
print(cluster_data)

# 5. Plot the clustering 
# ggplot(cluster_data,
#   aes(x=get(interested_features[1]), y=get(interested_features[2]), color=cluster))+
#   geom_point()+
#   labs(y = "Mortality Rate", x = "Percentage of Elderly Population", color="Cluster")+
#   geom_text(aes(label= ifelse(get(interested_features[1]) > quantile(get(interested_features[1]), 0.999),
#      as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
#   geom_text(aes(label= ifelse(get(interested_features[2]) > quantile(get(interested_features[2]), 0.999),
#      as.character(county_state),'')),hjust=-.1,vjust=0, size=2.5)+
#   scale_x_continuous(expand = c(.1, .1))+
#   scale_fill_discrete(name = "Cluster")

# 6. Geography Plot
plot_vs_county(cluster_data, "cluster", 
               percentile = F,
               banks = 11,
               legend_title = "Cluster",
               graphic_title = "Not Including COVID-19 Data DBSCAN Clustering")
Ignoring unknown parameters: name

Try Looking Statewise

Nothng really interesting with looking at county data. I could decrease the number of minimum points, but it seems like 10 counties for this is a good number. Much smaller there doesn’t seem like there is really a group. The noise points were the most interesting to me.

state.name[match("NY", state.abb)]
[1] "New York"
library(scales)
plot_states <- function(data, col_val, legend_title="", graphic_title="", font_size=15){
  sp <- select(data, region = region, col_val)
  print(sp)
  
  gusa <- map_data("state")
  print(gusa)
  
  gusa_pop <- left_join(gusa, sp, "region")
  print(gusa_pop)
  
  plt <- ggplot(gusa_pop) +
    geom_polygon(aes(long, lat, group = group, fill = get(col_val)), color="grey") +
    coord_map("bonne", parameters = 41.6) +
    ggthemes::theme_map()+
    theme(plot.title = element_text(family = "Helvetica", face = "bold", size = (font_size)))+
    # scale_fill_gradient(labels = percent)+
    scale_color_discrete()+
    # scale_fill_binned()+
    # scale_fill_discrete(name = "Cluster")+
    ggtitle(graphic_title)+
    labs(fill=legend_title)
  
  plt
}
agg_state_info_normed
minPts <- 3 #3 states must be in a cluster at a minimum

interested_features = c("pct_elderly", "mortality_rate")

# 1. Get the features we want to cluster with
cluster_features <- agg_state_info_normed %>% select(interested_features) %>%
  drop_na()

# 2. Find eps value
find_optimal_eps(cluster_features, k = minPts-1, line_pos = .7)


# 3. Make the clustering 
cluster_labeling <- cluster_features %>% dbscan(eps=.7, MinPts = minPts)
converting argument MinPts (fpc) to minPts (dbscan)!
# print(cluster_labeling)

# 4. Add clustering Labels
cluster_data <- add_cluster_columns(cluster_features, cluster_labeling)

# 5. (Depending) Add noise label
cluster_data <- add_noise_column(cluster_data)
# print(cluster_data)

# 6. Add back the county/state info
cluster_data <- agg_state_info_normed %>% mutate(cluster = cluster_data$cluster)
# print(cluster_data)

# 5. Plot the clustering 
ggplot(cluster_data,
  aes(x=get(interested_features[1]), y=get(interested_features[2]), color=cluster))+
  geom_point()+
  labs(y = "Normalized Mortality Rate", x = "Normalized Percentage of Elderly Population", color="Cluster")+
  geom_text(aes(label= ifelse(get(interested_features[1]) > quantile(get(interested_features[1]), 0.999),
     as.character(state),'')),hjust=-.3,vjust=0, size=2.5)+
  geom_text(aes(label= ifelse(get(interested_features[2]) > quantile(get(interested_features[2]), 0.999),
     as.character(state),'')),hjust=-.3,vjust=0, size=2.5)+
  geom_text(aes(label= ifelse(cluster == "Noise",
     as.character(state),'')),cex=2, hjust=1.5,vjust=-.6, size=2.5)+
  
  
  scale_x_continuous(expand = c(.1, .1))+
  scale_fill_discrete(name = "Cluster")
Duplicated aesthetics after name standardisation: size

# 6. Plot geography plot
# print(cluster_data)
plot_states(cluster_data %>% unique(), "cluster", legend_title = "Cluster", graphic_title = "Normalized Mortality Rate and Normalized Percentage Elderly Population DBSCAN Clustering", font_size = 11)

Doing this for all data. Can’t really visualize on scatterplot without dim reduction so avoiding that for now

minPts <- 3 #3 states must be in a cluster at a minimum

# 1. Get the features we want to cluster with
cluster_features <- agg_state_info_normed %>% select_if(is.numeric) %>%
  drop_na() %>% select(-one_of(c("pct_infected", "pct_deaths", "mortality_rate")))
cluster_features

# 2. Find eps value
find_optimal_eps(cluster_features, k = minPts-1, line_pos = 2.5)


# 3. Make the clustering 
cluster_labeling <- cluster_features %>% dbscan(eps=2.5, MinPts = minPts)
converting argument MinPts (fpc) to minPts (dbscan)!
# print(cluster_labeling)

# 4. Add clustering Labels
cluster_data <- add_cluster_columns(cluster_features, cluster_labeling)

# 5. (Depending) Add noise label
cluster_data <- add_noise_column(cluster_data)
# print(cluster_data)

# 6. Add back the county/state info
cluster_data <- agg_state_info_normed %>% mutate(cluster = cluster_data$cluster)
# print(cluster_data)

# 5. Plot the clustering 
# ggplot(cluster_data,
#   aes(x=get(interested_features[1]), y=get(interested_features[2]), color=cluster))+
#   geom_point()+
#   labs(y = "Mortality Rate", x = "Percentage of Elderly Population", color="Cluster")+
#   geom_text(aes(label= ifelse(get(interested_features[1]) > quantile(get(interested_features[1]), 0.999),
#      as.character(state),'')),hjust=-.3,vjust=0, size=2.5)+
#   geom_text(aes(label= ifelse(get(interested_features[2]) > quantile(get(interested_features[2]), 0.999),
#      as.character(state),'')),hjust=-.3,vjust=0, size=2.5)+
# 
#   geom_text(aes(label= ifelse(cluster == "Noise",
#      as.character(state),'')),hjust=-.3,vjust=-.2, size=2.5)+
#   
#   
#   scale_x_continuous(expand = c(.1, .1))+
#   scale_fill_discrete(name = "Cluster")

# 6. Plot geography plot
# print(cluster_data)
# Tring one without covid info to see if the states are that different
plot_states(cluster_data %>% unique(), "cluster", legend_title = "Cluster", graphic_title = "Not Including COVID-19 Data DBSCAN Clustering")

ut <- cluster_data %>% filter(state == "UT") %>%
  select(-one_of(c("region", "cluster"))) %>%
  pivot_longer(!state, names_to = "Feature")

ggplot(ut, aes(x = value, y = Feature))+
  geom_bar(stat="identity")+
  theme(plot.title = element_text(family = "Helvetica", face = "bold", size = (15)),
          legend.background = element_rect(fill = NA),
          legend.position = "left")+
  ggtitle("Utah Features")

  
  # facet_grid(rows=vars())

External Validation

get_bins <- function(df, col_name, bins){
  cut(100 * percent_rank(df[col_name]), seq(0, 100, len = bins),
                        include.lowest = TRUE)
}
# Example usage (say you want percentile bins for mortality rate--say 5 bins)

# Get the percentile cuts
mortality_rate_pcls <- agg_state_info_normed %>% get_bins("mortality_rate", 5)

# Add into the dataframe if you want 
new_df <- agg_state_info_normed %>% mutate(mortality_rate_pcls = mortality_rate_pcls)
new_df

# If you want it just as a class label
new_df <- new_df %>% mutate(class_label_pcls = as.integer(mortality_rate_pcls))
new_df
gusa <- map_data("state")
gusa %>% filter(region == "new york")
add_noise_column <- function(dbscan_df, noise_col=0){
  clustered_data <- dbscan_df %>% mutate(cluster = if_else(cluster == noise_col, "Noise", as.character(cluster)))
}


db_out <- dbscan(census_normed %>% select(pct_deaths, pct_elderly), eps = .15, minPts = 5)
db_out
pct_elderly_mortality_cluster <- census_normed %>% 
  select(pct_deaths, pct_elderly) %>%
  dbscan(eps=.15, MinPts = 5)
pct_elderly_mortality_cluster
pct_elderly_mortality_cluster
census_normed %>% 
  select(pct_deaths, pct_elderly) %>%
  mutate(cluster = as.factor(pct_elderly_mortality_cluster$cluster)) %>%
  select(cluster) %>% unique()
ggplot(census_normed %>% 
  select(pct_deaths, pct_elderly) %>%
  mutate(cluster = as.factor(pct_elderly_mortality_cluster$cluster)),
  aes(x=pct_elderly, y=pct_deaths, color=cluster))+
  geom_point()

PCA Dimensionality Reduction for Clustering

census_normed[rowSums(is.na(census_normed)) > 0, ]   
# PCA Dimensionality reduction 
census.pca <- prcomp(census_normed %>% select_if(is.double) %>% drop_na(),)
census.pca$scale
p <- 
  census.pca %>% 
  ggplot(aes(x = rotation, weight = relative_freq)) +
  geom_bar(width = 0.5, fill = "blue") +
  scale_x_discrete(limits = the_order) +
  scale_y_continuous(label = scales::percent) +
  geom_point(aes(x = reason, y = cumulative_freq)) +
  geom_line(aes(x = reason, y = cumulative_freq, group = 1)) +
  # NB: Must use "group = 1"
  labs(x = "", y = "Relative frequency", 
       title = "A Pareto diagram for reasons of 1000 accidents") +
  theme(plot.title = element_text(hjust = 0.5))
  # NB: Use theme to center the title

print(p)
fviz_eig(census.pca)
fviz_pca_var(census.pca,
             col.var = "contrib", # Color by contributions to the PC
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE     # Avoid text overlapping
             )
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBub3RlYm9vayBpcyBmb3Igd29ya2luZyB3aXRoIERCU0NBTiB1c2luZyB0aGUgdGliYmxlcyBjcmVhdGVkIGJ5IGBkYXRhX3Byb2Nlc3Npbmcucm1kYAoKYGBge3J9CiMgTG9hZCBpbiBsaWJyYXJpZXMgCmxpYnJhcnkoYmVlc3dhcm0pCmxpYnJhcnkobmFuaWFyKQpsaWJyYXJ5KHpvbykKbGlicmFyeShqYW5pdG9yKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHBseXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoR0dhbGx5KSAjIGZvciBnZ3BhaXJzCmxpYnJhcnkobHVicmlkYXRlKQoKIyBGb3IgUENBIHZpc3VhbGl6YXRpb24KIyBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikKbGlicmFyeShmYWN0b2V4dHJhKQojIGluc3RhbGwucGFja2FnZXMoImRic2NhbiIpCmxpYnJhcnkoZGJzY2FuKQpgYGAKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpwbG90X3ZzX2NvdW50eSA8LSBmdW5jdGlvbihkZiwgY29sX3ZhbCwgcGVyY2VudGlsZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlwc190aXRsZT0iY291bnR5X2ZpcHNfY29kZSIsIGJhbmtzPTYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmRfdGl0bGU9IiIsIGdyYXBoaWNfdGl0bGU9IiIpewogICMgU3Vic2V0IGZvciBzcGVlZCAKICBkZiA8LSBkZltjKGZpcHNfdGl0bGUsIGNvbF92YWwpXQogIAogICMgR2V0IGNvdW50eSBkYXRhCiAgZ2NvdW50eSA8LSBnZ3Bsb3QyOjptYXBfZGF0YSgiY291bnR5IikKICAjIFVTQSBtYXAgZGF0YQogIGd1c2EgPC0gbWFwX2RhdGEoInN0YXRlIikKCiAgaWYgKGJhbmtzID4gOSl7CiAgICBteWNvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIlJlZHMiKSkoYmFua3MpCiAgfQoKICAjIEZvcm1hdCB3aXRoIHN1YnJlZ2lvbnMKICBmaXBzdGFiIDwtCiAgICAgIHRyYW5zbXV0ZShtYXBzOjpjb3VudHkuZmlwcywgZmlwcywgY291bnR5ID0gc3ViKCI6LioiLCAiIiwgcG9seW5hbWUpKSAlPiUKICAgICAgdW5pcXVlKCkgJT4lCiAgICAgIHNlcGFyYXRlKGNvdW50eSwgYygicmVnaW9uIiwgInN1YnJlZ2lvbiIpLCBzZXAgPSAiLCIpCgogICMgQ29tYmluZSBpbiBkZXNpcmVkIG9yZGVyIChOQSBmb3IgbWlzc2luZykKICBnY291bnR5IDwtIGxlZnRfam9pbihnY291bnR5LCBmaXBzdGFiLCBjKCJyZWdpb24iLCAic3VicmVnaW9uIikpCgoKICBkaXMgPC0gZGYKICBkaXMkcnByb3AgPC0gcmFuayhkZltjb2xfdmFsXSkKICBkaXMkcGNscyA8LSBjdXQoMTAwICogcGVyY2VudF9yYW5rKGRmW2NvbF92YWxdKSwgc2VxKDAsIDEwMCwgbGVuID0gYmFua3MpLAogICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlLmxvd2VzdCA9IFRSVUUpCgogICMgTWlzc2luZyBkYXRhCiAgYW50aV9qb2luKGdjb3VudHksIGRpcywgYnkgPSBjKCJmaXBzIiA9IGZpcHNfdGl0bGUpKSAlPiUKICAgIHNlbGVjdChyZWdpb24sIHN1YnJlZ2lvbikgJT4lCiAgICB1bmlxdWUoKQogIGdjb3VudHlfcG9wIDwtIGxlZnRfam9pbihnY291bnR5LCBkaXMsIGJ5ID0gYygiZmlwcyIgPSBmaXBzX3RpdGxlKSkKICBmaWxsX3ZhbHMgPC0gZ2NvdW50eV9wb3BbY29sX3ZhbF0KCiAgIyBQbG90CiAgaWYgKGxlZ2VuZF90aXRsZSA9PSAiIil7CiAgICBsZWdlbmRfdGl0bGUgPC0gY29sX3ZhbAogIH0KCiAgaWYgKHBlcmNlbnRpbGUgPT0gRkFMU0UpewogICAgIyBuYW1lcyhnY291bnR5X3BvcClbbmFtZXMoZ2NvdW50eV9wb3ApID09IGNvbF92YWxdIDwtICJjb2xfb2ZfaW50ZXJlc3QiCiAgICBwbHQgPC0gZ2dwbG90KGdjb3VudHlfcG9wKSArCiAgICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gZ2V0KGNvbF92YWwpKSwKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkiLCBzaXplID0gMC4xLCBuYW1lPSJQZXJjZW50IEluZmVjdGVkIikgKwogICAgICBnZW9tX3BvbHlnb24oYWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgICAgICBmaWxsID0gTkEsIGRhdGEgPSBndXNhLCBjb2xvciA9ICJsaWdodGdyZXkiKSArCiAgICAgIGNvb3JkX21hcCgiYm9ubmUiLCBwYXJhbWV0ZXJzID0gNDEuNikgKyBnZ3RoZW1lczo6dGhlbWVfbWFwKCkgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWx2ZXRpY2EiLCBmYWNlID0gImJvbGQiLCBzaXplID0gKDE1KSksCiAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKQogICAgICAjIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCkKICAgICAgICMgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gInJlZCIsIG5hLnZhbHVlID0gImdyZXkiKQogICAgICAjIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSB0ZXJyYWluLmNvbG9ycygxMCkpCiAgfQoKaWYgKHBlcmNlbnRpbGUgPT0gVFJVRSl7CiAgcGx0IDwtIGdncGxvdChnY291bnR5X3BvcCkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBwY2xzKSwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5Iiwgc2l6ZSA9IDAuMSkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICAgIGZpbGwgPSBOQSwgZGF0YSA9IGd1c2EsIGNvbG9yID0gImxpZ2h0Z3JleSIpICsKICAgIGNvb3JkX21hcCgiYm9ubmUiLCBwYXJhbWV0ZXJzID0gNDEuNikgKyBnZ3RoZW1lczo6dGhlbWVfbWFwKCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbXljb2xvcnMsIG5hLnZhbHVlID0gImdyZXkiKSArCiAgICAjIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAidmlyaWRpcyIsIG5hLnZhbHVlID0gImdyZXkiKSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWx2ZXRpY2EiLCBmYWNlID0gImJvbGQiLCBzaXplID0gKDE1KSksCiAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKQogICAgfQogIHBsdCA8LSBwbHQgKyBsYWJzKGZpbGw9bGVnZW5kX3RpdGxlKSArIGdndGl0bGUoZ3JhcGhpY190aXRsZSkKICBwbHQKfQpgYGAKCgoKYGBge3J9CmNlbnN1c19ub3JtZWQgPC0gcmVhZF9jc3YoIi4vLi4vZGF0YXNldHMvY2Vuc3VzX25vcm1lZC5jc3YiKQphZ2dfc3RhdGVfaW5mb19ub3JtZWQgPC0gcmVhZF9jc3YoIi4vLi4vZGF0YXNldHMvYWdnX3N0YXRlX2luZm9fbm9ybWVkLmNzdiIpCgojIEFkZCBpbiBhY3R1YWwgc3RhdGUgbmFtZSB0byBhdm9pZCBpc3N1ZXMgCmFnZ19zdGF0ZV9pbmZvX25vcm1lZCRyZWdpb24gPSB0b2xvd2VyKHN0YXRlLm5hbWVbbWF0Y2goYWdnX3N0YXRlX2luZm9fbm9ybWVkJHN0YXRlLCBzdGF0ZS5hYmIpXSkKCmNlbnN1c19ub3JtZWQgPC0gY2Vuc3VzX25vcm1lZCAlPiUgbXV0YXRlKGNvdW50eV9maXBzX2NvZGUgPSBhcy5pbnRlZ2VyKGNvdW50eV9maXBzX2NvZGUpKQoKCiMgQWRkaW5nIGluIGNvdW50eSwgc3RhdGUgZm9yIGdyYXBocyBsYXRlcgojIFJlbW92ZSAnIGNvdW50eScKIyBHbyBhaGVhZCBhbmQgZHJvcCBuYSB2YWx1ZXMgdG8gYXZvaWQgaXNzdWVzIApjZW5zdXNfbm9ybWVkJGNvdW50eV9uYW1lIDwtIHN1YigiXFxzKkNvdW50eVxcYi4qIiwgIiIsIGNlbnN1c19ub3JtZWQkY291bnR5X25hbWUpCmNlbnN1c19ub3JtZWQgPC0gY2Vuc3VzX25vcm1lZCAlPiUgbXV0YXRlKGNvdW50eV9zdGF0ZSA9IHBhc3RlKGNvdW50eV9uYW1lLCBzdGF0ZSwgc2VwPSIsICIpKSAlPiUgZHJvcF9uYSgpCgpjZW5zdXNfbm9ybWVkCmFnZ19zdGF0ZV9pbmZvX25vcm1lZApgYGAKCmBgYHtyfQpjZW5zdXNfbm9ybWVkICU+JSBzZWxlY3RfaWYoaXMuZG91YmxlKQpgYGAKCgoKIyMgQ2x1c3RlciBXaXRoIERCU0NBTgoKRmlyc3QsIHdlIG5lZWQgdG8gZmluZCBhIGdvb2QgdmFsdWUgZm9yIGVwcy4gIE5vdGU6IG1pblB0cyBjb250YWlucyB0aGUgcG9pbnQgaXRzZWxmLCB3aGlsZSB0aGUgay1uZWFyZXN0IG5laWdoYm9yIGRvZXMgbm90LiBXZSB0aGVyZWZvcmUgaGF2ZSB0byB1c2UgayA9IG1pblB0cyAtIDEuICBJIHdpbGwgY3JlYXRlIGEgdXRpbGl0eSBmdW5jdGlvbiB0byBwbG90IHRvIGZpbmQgdGhlIGtuZWUgaW4gdGhlIGdyYXBoLCBidXQgdGhlIGRvdHRlZCBsaW5lIHdpbGwgaGF2ZSB0byBiZSBhZGp1c3RlZCBhZnRlciBpdCBpcyBmb3VuZC4KYGBge3J9CiMgTm90ZTogayBzaG91bGQgYmUgbWlucG9pbnRzIC0gMSEKZmluZF9vcHRpbWFsX2VwcyA8LSBmdW5jdGlvbihkYXRhLCBrLCBsaW5lX3Bvcz0uMzIpewogIGtOTmRpc3RwbG90KGRhdGEsIGsgPSBrKQogIGFibGluZShoID0gbGluZV9wb3MsIGNvbCA9ICJyZWQiLCBsdHk9MikKfQoKYWRkX2NsdXN0ZXJfY29sdW1ucyA8LSBmdW5jdGlvbihkYXRhLCBjbHVzdGVyX2RmKXsKICBkYXRhICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXJfZGYkY2x1c3RlcikKfQoKYWRkX25vaXNlX2NvbHVtbiA8LSBmdW5jdGlvbihkYnNjYW5fZGYsIG5vaXNlX2NvbD0wKXsKICBjbHVzdGVyZWRfZGF0YSA8LSBkYnNjYW5fZGYgJT4lIG11dGF0ZShjbHVzdGVyID0gaWZfZWxzZShjbHVzdGVyID09IG5vaXNlX2NvbCwgIk5vaXNlIiwgYXMuY2hhcmFjdGVyKGNsdXN0ZXIpKSkKfQoKIyBBZGQgYSBwbG90dGluZyBmdW5jdGlvbgpgYGAKCmBgYHtyfQojIFByb2NlZHVyZToKbWluUHRzIDwtIDEwICMxMCBjb3VudGllcyBtdXN0IGJlIGluIGEgY2x1c3RlciBhdCBhIG1pbmltdW0KCiMgMS4gR2V0IHRoZSBmZWF0dXJlcyB3ZSB3YW50IHRvIGNsdXN0ZXIgd2l0aApjbHVzdGVyX2ZlYXR1cmVzIDwtIGNlbnN1c19ub3JtZWQgJT4lIHNlbGVjdChtb3J0YWxpdHlfcmF0ZSwgcGN0X2VsZGVybHkpICU+JQogIGRyb3BfbmEoKQoKIyAyLiBGaW5kIGVwcyB2YWx1ZQpmaW5kX29wdGltYWxfZXBzKGNsdXN0ZXJfZmVhdHVyZXMsIGsgPSBtaW5QdHMtMSwgbGluZV9wb3MgPSAuNykKCiMgMy4gTWFrZSB0aGUgY2x1c3RlcmluZyAKY2x1c3Rlcl9sYWJlbGluZyA8LSBjbHVzdGVyX2ZlYXR1cmVzICU+JSBkYnNjYW4oZXBzPS43LCBNaW5QdHMgPSBtaW5QdHMpCnByaW50KGNsdXN0ZXJfbGFiZWxpbmcpCgojIDQuIEFkZCBjbHVzdGVyaW5nIExhYmVscwpjbHVzdGVyX2RhdGEgPC0gYWRkX2NsdXN0ZXJfY29sdW1ucyhjbHVzdGVyX2ZlYXR1cmVzLCBjbHVzdGVyX2xhYmVsaW5nKQoKIyA1LiAoRGVwZW5kaW5nKSBBZGQgbm9pc2UgbGFiZWwKY2x1c3Rlcl9kYXRhIDwtIGFkZF9ub2lzZV9jb2x1bW4oY2x1c3Rlcl9kYXRhKQoKIyA2LiBBZGQgYmFjayB0aGUgY291bnR5L3N0YXRlIGluZm8KY2x1c3Rlcl9kYXRhIDwtIGxlZnRfam9pbihjbHVzdGVyX2RhdGEsIGNlbnN1c19ub3JtZWQpCgojIDUuIFBsb3QgdGhlIGNsdXN0ZXJpbmcgCmdncGxvdChjbHVzdGVyX2RhdGEsCiAgYWVzKHg9cGN0X2VsZGVybHksIHk9bW9ydGFsaXR5X3JhdGUsIGNvbG9yPWNsdXN0ZXIpKSsKICBnZW9tX3BvaW50KCkrCiAgbGFicyh5ID0gIk1vcnRhbGl0eSBSYXRlIiwgeCA9ICJQZXJjZW50YWdlIG9mIEVsZGVybHkgUG9wdWxhdGlvbiIsIGNvbG9yPSJDbHVzdGVyIikrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gaWZlbHNlKG1vcnRhbGl0eV9yYXRlID4gcXVhbnRpbGUobW9ydGFsaXR5X3JhdGUsIDAuOTk5KSwKICAgICBhcy5jaGFyYWN0ZXIoY291bnR5X3N0YXRlKSwnJykpLGhqdXN0PS0uMSx2anVzdD0wLCBzaXplPTIuNSkrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gaWZlbHNlKHBjdF9lbGRlcmx5ID4gcXVhbnRpbGUocGN0X2VsZGVybHksIDAuOTk5KSwKICAgICBhcy5jaGFyYWN0ZXIoY291bnR5X3N0YXRlKSwnJykpLGhqdXN0PS0uMSx2anVzdD0wLCBzaXplPTIuNSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoLjEsIC4xKSkrCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gIkNsdXN0ZXIiKQoKIyA2LiBHZW9ncmFwaHkgUGxvdApwbG90X3ZzX2NvdW50eShjbHVzdGVyX2RhdGEsICJjbHVzdGVyIiwgCiAgICAgICAgICAgICAgIHBlcmNlbnRpbGUgPSBGLAogICAgICAgICAgICAgICBiYW5rcyA9IDExLAogICAgICAgICAgICAgICBsZWdlbmRfdGl0bGUgPSAiQ2x1c3RlciIsCiAgICAgICAgICAgICAgIGdyYXBoaWNfdGl0bGUgPSAiTW9ydGFsaXR5IFJhdGUgdnMuIFBlcmNlbnRhZ2UgRWxkZXJseSBQb3B1bGF0aW9uIENsdXN0ZXJpbmciKQpgYGAKTm9uIHVzZWZ1bCBvbmVzIHRoYXQgbG9vayB0aGUgZXhhY3Qgc2FtZToKCiogTW9ydGFsaXR5IHJhdGUgd2l0aDoKICAqIG1lZGlhbl9pbmNvbWUKICAKYGBge3J9CmNlbnN1c19ub3JtZWQKYGBgCgpgYGB7cn0KIyBQcm9jZWR1cmU6Cm1pblB0cyA8LSAxMCAjMTAgY291bnRpZXMgbXVzdCBiZSBpbiBhIGNsdXN0ZXIgYXQgYSBtaW5pbXVtCgppbnRlcmVzdGVkX2ZlYXR1cmVzID0gYygibWVkaWFuX2FnZSIsICJtb3J0YWxpdHlfcmF0ZSIpCgojIDEuIEdldCB0aGUgZmVhdHVyZXMgd2Ugd2FudCB0byBjbHVzdGVyIHdpdGgKY2x1c3Rlcl9mZWF0dXJlcyA8LSBjZW5zdXNfbm9ybWVkICU+JSBzZWxlY3QoaW50ZXJlc3RlZF9mZWF0dXJlcykgJT4lCiAgZHJvcF9uYSgpCgojIDIuIEZpbmQgZXBzIHZhbHVlCmZpbmRfb3B0aW1hbF9lcHMoY2x1c3Rlcl9mZWF0dXJlcywgayA9IG1pblB0cy0xLCBsaW5lX3BvcyA9IC43KQoKIyAzLiBNYWtlIHRoZSBjbHVzdGVyaW5nIApjbHVzdGVyX2xhYmVsaW5nIDwtIGNsdXN0ZXJfZmVhdHVyZXMgJT4lIGRic2NhbihlcHM9LjcsIE1pblB0cyA9IG1pblB0cykKcHJpbnQoY2x1c3Rlcl9sYWJlbGluZykKCiMgNC4gQWRkIGNsdXN0ZXJpbmcgTGFiZWxzCmNsdXN0ZXJfZGF0YSA8LSBhZGRfY2x1c3Rlcl9jb2x1bW5zKGNsdXN0ZXJfZmVhdHVyZXMsIGNsdXN0ZXJfbGFiZWxpbmcpCgojIDUuIChEZXBlbmRpbmcpIEFkZCBub2lzZSBsYWJlbApjbHVzdGVyX2RhdGEgPC0gYWRkX25vaXNlX2NvbHVtbihjbHVzdGVyX2RhdGEpCiMgcHJpbnQoY2x1c3Rlcl9kYXRhKQoKIyA2LiBBZGQgYmFjayB0aGUgY291bnR5L3N0YXRlIGluZm8KIyBjbHVzdGVyX2RhdGEgPC0gbGVmdF9qb2luKGNsdXN0ZXJfZGF0YSwgY2Vuc3VzX25vcm1lZCwgYnk9c3RhdGVfY291bnR5KQpjbHVzdGVyX2RhdGEgPC0gY2Vuc3VzX25vcm1lZCAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RhdGEkY2x1c3RlcikKcHJpbnQoY2x1c3Rlcl9kYXRhKQoKIyA1LiBQbG90IHRoZSBjbHVzdGVyaW5nIApnZ3Bsb3QoY2x1c3Rlcl9kYXRhLAogIGFlcyh4PWdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzFdKSwgeT1nZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1syXSksIGNvbG9yPWNsdXN0ZXIpKSsKICBnZW9tX3BvaW50KCkrCiAgbGFicyh5ID0gIk1vcnRhbGl0eSBSYXRlIiwgeCA9ICJNZWRpYW4gQWdlIiwgY29sb3I9IkNsdXN0ZXIiKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPSBpZmVsc2UoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pID4gcXVhbnRpbGUoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pLCAwLjk5OSksCiAgICAgYXMuY2hhcmFjdGVyKGNvdW50eV9zdGF0ZSksJycpKSxoanVzdD0tLjEsdmp1c3Q9MCwgc2l6ZT0yLjUpKwogIGdlb21fdGV4dChhZXMobGFiZWw9IGlmZWxzZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1syXSkgPiBxdWFudGlsZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1syXSksIDAuOTk5KSwKICAgICBhcy5jaGFyYWN0ZXIoY291bnR5X3N0YXRlKSwnJykpLGhqdXN0PS0uMSx2anVzdD0wLCBzaXplPTIuNSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoLjEsIC4xKSkrCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gIkNsdXN0ZXIiKQoKIyA2LiBHZW9ncmFwaHkgUGxvdApwbG90X3ZzX2NvdW50eShjbHVzdGVyX2RhdGEsICJjbHVzdGVyIiwgCiAgICAgICAgICAgICAgIHBlcmNlbnRpbGUgPSBGLAogICAgICAgICAgICAgICBiYW5rcyA9IDExLAogICAgICAgICAgICAgICBsZWdlbmRfdGl0bGUgPSAiQ2x1c3RlciIsCiAgICAgICAgICAgICAgIGdyYXBoaWNfdGl0bGUgPSAiTWVkaWFuIEFnZSBhbmQgTW9ydGFsaXR5IFJhdGUgQ2x1c3RlcmluZyIpCmBgYApgYGB7cn0KIyBQcm9jZWR1cmU6Cm1pblB0cyA8LSAxMCAjMTAgY291bnRpZXMgbXVzdCBiZSBpbiBhIGNsdXN0ZXIgYXQgYSBtaW5pbXVtCgojIDEuIEdldCB0aGUgZmVhdHVyZXMgd2Ugd2FudCB0byBjbHVzdGVyIHdpdGgKY2x1c3Rlcl9mZWF0dXJlcyA8LSBjZW5zdXNfbm9ybWVkICU+JSBzZWxlY3RfaWYoaXMuZG91YmxlKSAlPiUKICBkcm9wX25hKCkgJT4lIHNlbGVjdCgtb25lX29mKGMoInBjdF9pbmZlY3RlZCIsICJwY3RfZGVhdGhzIiwgIm1vcnRhbGl0eV9yYXRlIikpKQoKIyAyLiBGaW5kIGVwcyB2YWx1ZQpmaW5kX29wdGltYWxfZXBzKGNsdXN0ZXJfZmVhdHVyZXMsIGsgPSBtaW5QdHMtMSwgbGluZV9wb3MgPSAuNykKCiMgMy4gTWFrZSB0aGUgY2x1c3RlcmluZyAKY2x1c3Rlcl9sYWJlbGluZyA8LSBjbHVzdGVyX2ZlYXR1cmVzICU+JSBkYnNjYW4oZXBzPS43LCBNaW5QdHMgPSBtaW5QdHMpCnByaW50KGNsdXN0ZXJfbGFiZWxpbmcpCgojIDQuIEFkZCBjbHVzdGVyaW5nIExhYmVscwpjbHVzdGVyX2RhdGEgPC0gYWRkX2NsdXN0ZXJfY29sdW1ucyhjbHVzdGVyX2ZlYXR1cmVzLCBjbHVzdGVyX2xhYmVsaW5nKQoKIyA1LiAoRGVwZW5kaW5nKSBBZGQgbm9pc2UgbGFiZWwKY2x1c3Rlcl9kYXRhIDwtIGFkZF9ub2lzZV9jb2x1bW4oY2x1c3Rlcl9kYXRhKQojIHByaW50KGNsdXN0ZXJfZGF0YSkKCiMgNi4gQWRkIGJhY2sgdGhlIGNvdW50eS9zdGF0ZSBpbmZvCiMgY2x1c3Rlcl9kYXRhIDwtIGxlZnRfam9pbihjbHVzdGVyX2RhdGEsIGNlbnN1c19ub3JtZWQsIGJ5PXN0YXRlX2NvdW50eSkKY2x1c3Rlcl9kYXRhIDwtIGNlbnN1c19ub3JtZWQgJT4lIG11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kYXRhJGNsdXN0ZXIpCnByaW50KGNsdXN0ZXJfZGF0YSkKCiMgNS4gUGxvdCB0aGUgY2x1c3RlcmluZyAKIyBnZ3Bsb3QoY2x1c3Rlcl9kYXRhLAojICAgYWVzKHg9Z2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pLCB5PWdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzJdKSwgY29sb3I9Y2x1c3RlcikpKwojICAgZ2VvbV9wb2ludCgpKwojICAgbGFicyh5ID0gIk1vcnRhbGl0eSBSYXRlIiwgeCA9ICJQZXJjZW50YWdlIG9mIEVsZGVybHkgUG9wdWxhdGlvbiIsIGNvbG9yPSJDbHVzdGVyIikrCiMgICBnZW9tX3RleHQoYWVzKGxhYmVsPSBpZmVsc2UoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pID4gcXVhbnRpbGUoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pLCAwLjk5OSksCiMgICAgICBhcy5jaGFyYWN0ZXIoY291bnR5X3N0YXRlKSwnJykpLGhqdXN0PS0uMSx2anVzdD0wLCBzaXplPTIuNSkrCiMgICBnZW9tX3RleHQoYWVzKGxhYmVsPSBpZmVsc2UoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMl0pID4gcXVhbnRpbGUoZ2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMl0pLCAwLjk5OSksCiMgICAgICBhcy5jaGFyYWN0ZXIoY291bnR5X3N0YXRlKSwnJykpLGhqdXN0PS0uMSx2anVzdD0wLCBzaXplPTIuNSkrCiMgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYyguMSwgLjEpKSsKIyAgIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDbHVzdGVyIikKCiMgNi4gR2VvZ3JhcGh5IFBsb3QKcGxvdF92c19jb3VudHkoY2x1c3Rlcl9kYXRhLCAiY2x1c3RlciIsIAogICAgICAgICAgICAgICBwZXJjZW50aWxlID0gRiwKICAgICAgICAgICAgICAgYmFua3MgPSAxMSwKICAgICAgICAgICAgICAgbGVnZW5kX3RpdGxlID0gIkNsdXN0ZXIiLAogICAgICAgICAgICAgICBncmFwaGljX3RpdGxlID0gIk5vdCBJbmNsdWRpbmcgQ09WSUQtMTkgRGF0YSBEQlNDQU4gQ2x1c3RlcmluZyIpCmBgYAoKCgoKIyMgVHJ5IExvb2tpbmcgU3RhdGV3aXNlCgpOb3RobmcgcmVhbGx5IGludGVyZXN0aW5nIHdpdGggbG9va2luZyBhdCBjb3VudHkgZGF0YS4gIEkgY291bGQgZGVjcmVhc2UgdGhlIG51bWJlciBvZiBtaW5pbXVtIHBvaW50cywgYnV0IGl0IHNlZW1zIGxpa2UgMTAgY291bnRpZXMgZm9yIHRoaXMgaXMgYSBnb29kIG51bWJlci4gIE11Y2ggc21hbGxlciB0aGVyZSBkb2Vzbid0IHNlZW0gbGlrZSB0aGVyZSBpcyByZWFsbHkgYSBncm91cC4gIFRoZSBub2lzZSBwb2ludHMgd2VyZSB0aGUgbW9zdCBpbnRlcmVzdGluZyB0byBtZS4KCmBgYHtyfQpzdGF0ZS5uYW1lW21hdGNoKCJOWSIsIHN0YXRlLmFiYildCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KHNjYWxlcykKcGxvdF9zdGF0ZXMgPC0gZnVuY3Rpb24oZGF0YSwgY29sX3ZhbCwgbGVnZW5kX3RpdGxlPSIiLCBncmFwaGljX3RpdGxlPSIiLCBmb250X3NpemU9MTUpewogIHNwIDwtIHNlbGVjdChkYXRhLCByZWdpb24gPSByZWdpb24sIGNvbF92YWwpCiAgcHJpbnQoc3ApCiAgCiAgZ3VzYSA8LSBtYXBfZGF0YSgic3RhdGUiKQogIHByaW50KGd1c2EpCiAgCiAgZ3VzYV9wb3AgPC0gbGVmdF9qb2luKGd1c2EsIHNwLCAicmVnaW9uIikKICBwcmludChndXNhX3BvcCkKICAKICBwbHQgPC0gZ2dwbG90KGd1c2FfcG9wKSArCiAgICBnZW9tX3BvbHlnb24oYWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCwgZmlsbCA9IGdldChjb2xfdmFsKSksIGNvbG9yPSJncmV5IikgKwogICAgY29vcmRfbWFwKCJib25uZSIsIHBhcmFtZXRlcnMgPSA0MS42KSArCiAgICBnZ3RoZW1lczo6dGhlbWVfbWFwKCkrCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWx2ZXRpY2EiLCBmYWNlID0gImJvbGQiLCBzaXplID0gKGZvbnRfc2l6ZSkpKSsKICAgICMgc2NhbGVfZmlsbF9ncmFkaWVudChsYWJlbHMgPSBwZXJjZW50KSsKICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKCkrCiAgICAjIHNjYWxlX2ZpbGxfYmlubmVkKCkrCiAgICAjIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDbHVzdGVyIikrCiAgICBnZ3RpdGxlKGdyYXBoaWNfdGl0bGUpKwogICAgbGFicyhmaWxsPWxlZ2VuZF90aXRsZSkKICAKICBwbHQKfQoKYGBgCgpgYGB7cn0KYWdnX3N0YXRlX2luZm9fbm9ybWVkCmBgYAoKCgpgYGB7cn0KbWluUHRzIDwtIDMgIzMgc3RhdGVzIG11c3QgYmUgaW4gYSBjbHVzdGVyIGF0IGEgbWluaW11bQoKaW50ZXJlc3RlZF9mZWF0dXJlcyA9IGMoInBjdF9lbGRlcmx5IiwgIm1vcnRhbGl0eV9yYXRlIikKCiMgMS4gR2V0IHRoZSBmZWF0dXJlcyB3ZSB3YW50IHRvIGNsdXN0ZXIgd2l0aApjbHVzdGVyX2ZlYXR1cmVzIDwtIGFnZ19zdGF0ZV9pbmZvX25vcm1lZCAlPiUgc2VsZWN0KGludGVyZXN0ZWRfZmVhdHVyZXMpICU+JQogIGRyb3BfbmEoKQoKIyAyLiBGaW5kIGVwcyB2YWx1ZQpmaW5kX29wdGltYWxfZXBzKGNsdXN0ZXJfZmVhdHVyZXMsIGsgPSBtaW5QdHMtMSwgbGluZV9wb3MgPSAuNykKCiMgMy4gTWFrZSB0aGUgY2x1c3RlcmluZyAKY2x1c3Rlcl9sYWJlbGluZyA8LSBjbHVzdGVyX2ZlYXR1cmVzICU+JSBkYnNjYW4oZXBzPS43LCBNaW5QdHMgPSBtaW5QdHMpCiMgcHJpbnQoY2x1c3Rlcl9sYWJlbGluZykKCiMgNC4gQWRkIGNsdXN0ZXJpbmcgTGFiZWxzCmNsdXN0ZXJfZGF0YSA8LSBhZGRfY2x1c3Rlcl9jb2x1bW5zKGNsdXN0ZXJfZmVhdHVyZXMsIGNsdXN0ZXJfbGFiZWxpbmcpCgojIDUuIChEZXBlbmRpbmcpIEFkZCBub2lzZSBsYWJlbApjbHVzdGVyX2RhdGEgPC0gYWRkX25vaXNlX2NvbHVtbihjbHVzdGVyX2RhdGEpCiMgcHJpbnQoY2x1c3Rlcl9kYXRhKQoKIyA2LiBBZGQgYmFjayB0aGUgY291bnR5L3N0YXRlIGluZm8KY2x1c3Rlcl9kYXRhIDwtIGFnZ19zdGF0ZV9pbmZvX25vcm1lZCAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RhdGEkY2x1c3RlcikKIyBwcmludChjbHVzdGVyX2RhdGEpCgojIDUuIFBsb3QgdGhlIGNsdXN0ZXJpbmcgCmdncGxvdChjbHVzdGVyX2RhdGEsCiAgYWVzKHg9Z2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMV0pLCB5PWdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzJdKSwgY29sb3I9Y2x1c3RlcikpKwogIGdlb21fcG9pbnQoKSsKICBsYWJzKHkgPSAiTm9ybWFsaXplZCBNb3J0YWxpdHkgUmF0ZSIsIHggPSAiTm9ybWFsaXplZCBQZXJjZW50YWdlIG9mIEVsZGVybHkgUG9wdWxhdGlvbiIsIGNvbG9yPSJDbHVzdGVyIikrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gaWZlbHNlKGdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzFdKSA+IHF1YW50aWxlKGdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzFdKSwgMC45OTkpLAogICAgIGFzLmNoYXJhY3RlcihzdGF0ZSksJycpKSxoanVzdD0tLjMsdmp1c3Q9MCwgc2l6ZT0yLjUpKwogIGdlb21fdGV4dChhZXMobGFiZWw9IGlmZWxzZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1syXSkgPiBxdWFudGlsZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1syXSksIDAuOTk5KSwKICAgICBhcy5jaGFyYWN0ZXIoc3RhdGUpLCcnKSksaGp1c3Q9LS4zLHZqdXN0PTAsIHNpemU9Mi41KSsKICBnZW9tX3RleHQoYWVzKGxhYmVsPSBpZmVsc2UoY2x1c3RlciA9PSAiTm9pc2UiLAogICAgIGFzLmNoYXJhY3RlcihzdGF0ZSksJycpKSxjZXg9MiwgaGp1c3Q9MS41LHZqdXN0PS0uNiwgc2l6ZT0yLjUpKwogIAogIAogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKC4xLCAuMSkpKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDbHVzdGVyIikKCiMgNi4gUGxvdCBnZW9ncmFwaHkgcGxvdAojIHByaW50KGNsdXN0ZXJfZGF0YSkKcGxvdF9zdGF0ZXMoY2x1c3Rlcl9kYXRhICU+JSB1bmlxdWUoKSwgImNsdXN0ZXIiLCBsZWdlbmRfdGl0bGUgPSAiQ2x1c3RlciIsIGdyYXBoaWNfdGl0bGUgPSAiTm9ybWFsaXplZCBNb3J0YWxpdHkgUmF0ZSBhbmQgTm9ybWFsaXplZCBQZXJjZW50YWdlIEVsZGVybHkgUG9wdWxhdGlvbiBEQlNDQU4gQ2x1c3RlcmluZyIsIGZvbnRfc2l6ZSA9IDExKQpgYGAKCgoKCkRvaW5nIHRoaXMgZm9yIGFsbCBkYXRhLiAgQ2FuJ3QgcmVhbGx5IHZpc3VhbGl6ZSBvbiBzY2F0dGVycGxvdCB3aXRob3V0IGRpbSByZWR1Y3Rpb24gc28gYXZvaWRpbmcgdGhhdCBmb3Igbm93CmBgYHtyfQptaW5QdHMgPC0gMyAjMyBzdGF0ZXMgbXVzdCBiZSBpbiBhIGNsdXN0ZXIgYXQgYSBtaW5pbXVtCgojIDEuIEdldCB0aGUgZmVhdHVyZXMgd2Ugd2FudCB0byBjbHVzdGVyIHdpdGgKY2x1c3Rlcl9mZWF0dXJlcyA8LSBhZ2dfc3RhdGVfaW5mb19ub3JtZWQgJT4lIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUKICBkcm9wX25hKCkgJT4lIHNlbGVjdCgtb25lX29mKGMoInBjdF9pbmZlY3RlZCIsICJwY3RfZGVhdGhzIiwgIm1vcnRhbGl0eV9yYXRlIikpKQpjbHVzdGVyX2ZlYXR1cmVzCgojIDIuIEZpbmQgZXBzIHZhbHVlCmZpbmRfb3B0aW1hbF9lcHMoY2x1c3Rlcl9mZWF0dXJlcywgayA9IG1pblB0cy0xLCBsaW5lX3BvcyA9IDIuNSkKCiMgMy4gTWFrZSB0aGUgY2x1c3RlcmluZyAKY2x1c3Rlcl9sYWJlbGluZyA8LSBjbHVzdGVyX2ZlYXR1cmVzICU+JSBkYnNjYW4oZXBzPTIuNSwgTWluUHRzID0gbWluUHRzKQojIHByaW50KGNsdXN0ZXJfbGFiZWxpbmcpCgojIDQuIEFkZCBjbHVzdGVyaW5nIExhYmVscwpjbHVzdGVyX2RhdGEgPC0gYWRkX2NsdXN0ZXJfY29sdW1ucyhjbHVzdGVyX2ZlYXR1cmVzLCBjbHVzdGVyX2xhYmVsaW5nKQoKIyA1LiAoRGVwZW5kaW5nKSBBZGQgbm9pc2UgbGFiZWwKY2x1c3Rlcl9kYXRhIDwtIGFkZF9ub2lzZV9jb2x1bW4oY2x1c3Rlcl9kYXRhKQojIHByaW50KGNsdXN0ZXJfZGF0YSkKCiMgNi4gQWRkIGJhY2sgdGhlIGNvdW50eS9zdGF0ZSBpbmZvCmNsdXN0ZXJfZGF0YSA8LSBhZ2dfc3RhdGVfaW5mb19ub3JtZWQgJT4lIG11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kYXRhJGNsdXN0ZXIpCiMgcHJpbnQoY2x1c3Rlcl9kYXRhKQoKIyA1LiBQbG90IHRoZSBjbHVzdGVyaW5nIAojIGdncGxvdChjbHVzdGVyX2RhdGEsCiMgICBhZXMoeD1nZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1sxXSksIHk9Z2V0KGludGVyZXN0ZWRfZmVhdHVyZXNbMl0pLCBjb2xvcj1jbHVzdGVyKSkrCiMgICBnZW9tX3BvaW50KCkrCiMgICBsYWJzKHkgPSAiTW9ydGFsaXR5IFJhdGUiLCB4ID0gIlBlcmNlbnRhZ2Ugb2YgRWxkZXJseSBQb3B1bGF0aW9uIiwgY29sb3I9IkNsdXN0ZXIiKSsKIyAgIGdlb21fdGV4dChhZXMobGFiZWw9IGlmZWxzZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1sxXSkgPiBxdWFudGlsZShnZXQoaW50ZXJlc3RlZF9mZWF0dXJlc1sxXSksIDAuOTk5KSwKIyAgICAgIGFzLmNoYXJhY3RlcihzdGF0ZSksJycpKSxoanVzdD0tLjMsdmp1c3Q9MCwgc2l6ZT0yLjUpKwojICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gaWZlbHNlKGdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzJdKSA+IHF1YW50aWxlKGdldChpbnRlcmVzdGVkX2ZlYXR1cmVzWzJdKSwgMC45OTkpLAojICAgICAgYXMuY2hhcmFjdGVyKHN0YXRlKSwnJykpLGhqdXN0PS0uMyx2anVzdD0wLCBzaXplPTIuNSkrCiMgCiMgICBnZW9tX3RleHQoYWVzKGxhYmVsPSBpZmVsc2UoY2x1c3RlciA9PSAiTm9pc2UiLAojICAgICAgYXMuY2hhcmFjdGVyKHN0YXRlKSwnJykpLGhqdXN0PS0uMyx2anVzdD0tLjIsIHNpemU9Mi41KSsKIyAgIAojICAgCiMgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYyguMSwgLjEpKSsKIyAgIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDbHVzdGVyIikKCiMgNi4gUGxvdCBnZW9ncmFwaHkgcGxvdAojIHByaW50KGNsdXN0ZXJfZGF0YSkKIyBUcmluZyBvbmUgd2l0aG91dCBjb3ZpZCBpbmZvIHRvIHNlZSBpZiB0aGUgc3RhdGVzIGFyZSB0aGF0IGRpZmZlcmVudApwbG90X3N0YXRlcyhjbHVzdGVyX2RhdGEgJT4lIHVuaXF1ZSgpLCAiY2x1c3RlciIsIGxlZ2VuZF90aXRsZSA9ICJDbHVzdGVyIiwgZ3JhcGhpY190aXRsZSA9ICJOb3QgSW5jbHVkaW5nIENPVklELTE5IERhdGEgREJTQ0FOIENsdXN0ZXJpbmciKQpgYGAKCmBgYHtyfQp1dCA8LSBjbHVzdGVyX2RhdGEgJT4lIGZpbHRlcihzdGF0ZSA9PSAiVVQiKSAlPiUKICBzZWxlY3QoLW9uZV9vZihjKCJyZWdpb24iLCAiY2x1c3RlciIpKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKCFzdGF0ZSwgbmFtZXNfdG8gPSAiRmVhdHVyZSIpCgpnZ3Bsb3QodXQsIGFlcyh4ID0gdmFsdWUsIHkgPSBGZWF0dXJlKSkrCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWx2ZXRpY2EiLCBmYWNlID0gImJvbGQiLCBzaXplID0gKDE1KSksCiAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKSsKICBnZ3RpdGxlKCJVdGFoIEZlYXR1cmVzIikKICAKICAjIGZhY2V0X2dyaWQocm93cz12YXJzKCkpCmBgYAoKCiMgRXh0ZXJuYWwgVmFsaWRhdGlvbgpgYGB7cn0KZ2V0X2JpbnMgPC0gZnVuY3Rpb24oZGYsIGNvbF9uYW1lLCBiaW5zKXsKICBjdXQoMTAwICogcGVyY2VudF9yYW5rKGRmW2NvbF9uYW1lXSksIHNlcSgwLCAxMDAsIGxlbiA9IGJpbnMpLAogICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlLmxvd2VzdCA9IFRSVUUpCn0KIyBFeGFtcGxlIHVzYWdlIChzYXkgeW91IHdhbnQgcGVyY2VudGlsZSBiaW5zIGZvciBtb3J0YWxpdHkgcmF0ZS0tc2F5IDUgYmlucykKCiMgR2V0IHRoZSBwZXJjZW50aWxlIGN1dHMKbW9ydGFsaXR5X3JhdGVfcGNscyA8LSBhZ2dfc3RhdGVfaW5mb19ub3JtZWQgJT4lIGdldF9iaW5zKCJtb3J0YWxpdHlfcmF0ZSIsIDUpCgojIEFkZCBpbnRvIHRoZSBkYXRhZnJhbWUgaWYgeW91IHdhbnQgCm5ld19kZiA8LSBhZ2dfc3RhdGVfaW5mb19ub3JtZWQgJT4lIG11dGF0ZShtb3J0YWxpdHlfcmF0ZV9wY2xzID0gbW9ydGFsaXR5X3JhdGVfcGNscykKbmV3X2RmCgojIElmIHlvdSB3YW50IGl0IGp1c3QgYXMgYSBjbGFzcyBsYWJlbApuZXdfZGYgPC0gbmV3X2RmICU+JSBtdXRhdGUoY2xhc3NfbGFiZWxfcGNscyA9IGFzLmludGVnZXIobW9ydGFsaXR5X3JhdGVfcGNscykpCm5ld19kZgpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCmBgYHtyfQpndXNhIDwtIG1hcF9kYXRhKCJzdGF0ZSIpCmd1c2EgJT4lIGZpbHRlcihyZWdpb24gPT0gIm5ldyB5b3JrIikKYGBgCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKYGBge3J9CmFkZF9ub2lzZV9jb2x1bW4gPC0gZnVuY3Rpb24oZGJzY2FuX2RmLCBub2lzZV9jb2w9MCl7CiAgY2x1c3RlcmVkX2RhdGEgPC0gZGJzY2FuX2RmICU+JSBtdXRhdGUoY2x1c3RlciA9IGlmX2Vsc2UoY2x1c3RlciA9PSBub2lzZV9jb2wsICJOb2lzZSIsIGFzLmNoYXJhY3RlcihjbHVzdGVyKSkpCn0KCgpkYl9vdXQgPC0gZGJzY2FuKGNlbnN1c19ub3JtZWQgJT4lIHNlbGVjdChwY3RfZGVhdGhzLCBwY3RfZWxkZXJseSksIGVwcyA9IC4xNSwgbWluUHRzID0gNSkKZGJfb3V0CmBgYAoKCmBgYHtyfQoKYGBgCgoKCgoKCgoKCgoKCgoKCgpgYGB7cn0KcGN0X2VsZGVybHlfbW9ydGFsaXR5X2NsdXN0ZXIgPC0gY2Vuc3VzX25vcm1lZCAlPiUgCiAgc2VsZWN0KHBjdF9kZWF0aHMsIHBjdF9lbGRlcmx5KSAlPiUKICBkYnNjYW4oZXBzPS4xNSwgTWluUHRzID0gNSkKcGN0X2VsZGVybHlfbW9ydGFsaXR5X2NsdXN0ZXIKYGBgCmBgYHtyfQpwY3RfZWxkZXJseV9tb3J0YWxpdHlfY2x1c3RlcgpgYGAKCgpgYGB7cn0KY2Vuc3VzX25vcm1lZCAlPiUgCiAgc2VsZWN0KHBjdF9kZWF0aHMsIHBjdF9lbGRlcmx5KSAlPiUKICBtdXRhdGUoY2x1c3RlciA9IGFzLmZhY3RvcihwY3RfZWxkZXJseV9tb3J0YWxpdHlfY2x1c3RlciRjbHVzdGVyKSkgJT4lCiAgc2VsZWN0KGNsdXN0ZXIpICU+JSB1bmlxdWUoKQpgYGAKCgoKCmBgYHtyfQpnZ3Bsb3QoY2Vuc3VzX25vcm1lZCAlPiUgCiAgc2VsZWN0KHBjdF9kZWF0aHMsIHBjdF9lbGRlcmx5KSAlPiUKICBtdXRhdGUoY2x1c3RlciA9IGFzLmZhY3RvcihwY3RfZWxkZXJseV9tb3J0YWxpdHlfY2x1c3RlciRjbHVzdGVyKSksCiAgYWVzKHg9cGN0X2VsZGVybHksIHk9cGN0X2RlYXRocywgY29sb3I9Y2x1c3RlcikpKwogIGdlb21fcG9pbnQoKQpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgojIyBQQ0EgRGltZW5zaW9uYWxpdHkgUmVkdWN0aW9uIGZvciBDbHVzdGVyaW5nCgpgYGB7cn0KY2Vuc3VzX25vcm1lZFtyb3dTdW1zKGlzLm5hKGNlbnN1c19ub3JtZWQpKSA+IDAsIF0gICAKYGBgCgpgYGB7cn0KIyBQQ0EgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIApjZW5zdXMucGNhIDwtIHByY29tcChjZW5zdXNfbm9ybWVkICU+JSBzZWxlY3RfaWYoaXMuZG91YmxlKSAlPiUgZHJvcF9uYSgpLCkKYGBgCmBgYHtyfQpjZW5zdXMucGNhJHNjYWxlCmBgYAoKYGBge3J9CnAgPC0gCiAgY2Vuc3VzLnBjYSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcm90YXRpb24sIHdlaWdodCA9IHJlbGF0aXZlX2ZyZXEpKSArCiAgZ2VvbV9iYXIod2lkdGggPSAwLjUsIGZpbGwgPSAiYmx1ZSIpICsKICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IHRoZV9vcmRlcikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbCA9IHNjYWxlczo6cGVyY2VudCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSByZWFzb24sIHkgPSBjdW11bGF0aXZlX2ZyZXEpKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gcmVhc29uLCB5ID0gY3VtdWxhdGl2ZV9mcmVxLCBncm91cCA9IDEpKSArCiAgIyBOQjogTXVzdCB1c2UgImdyb3VwID0gMSIKICBsYWJzKHggPSAiIiwgeSA9ICJSZWxhdGl2ZSBmcmVxdWVuY3kiLCAKICAgICAgIHRpdGxlID0gIkEgUGFyZXRvIGRpYWdyYW0gZm9yIHJlYXNvbnMgb2YgMTAwMCBhY2NpZGVudHMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgIyBOQjogVXNlIHRoZW1lIHRvIGNlbnRlciB0aGUgdGl0bGUKCnByaW50KHApCmBgYAoKCmBgYHtyfQpmdml6X2VpZyhjZW5zdXMucGNhKQpgYGAKCgoKYGBge3J9CmZ2aXpfcGNhX3ZhcihjZW5zdXMucGNhLAogICAgICAgICAgICAgY29sLnZhciA9ICJjb250cmliIiwgIyBDb2xvciBieSBjb250cmlidXRpb25zIHRvIHRoZSBQQwogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoIiMwMEFGQkIiLCAiI0U3QjgwMCIsICIjRkM0RTA3IiksCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUgICAgICMgQXZvaWQgdGV4dCBvdmVybGFwcGluZwogICAgICAgICAgICAgKQpgYGAKCg==